package com.googlecode.ebasys.dao.hibernate;

import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.googlecode.ebasys.dao.EntityDao;
import com.googlecode.ebasys.entity.PersistentEntity;

/**
 * Universal Hibernate JPA implementation of {@link EntityDao}.
 * 
 * Extend it to provide concrete DAOs for concrete {@link PersistentEntity} 
 * types. You should add new entity-specific functionality in those concrete 
 * implementations.
 *
 * @author Tomas Varaneckas
 * @version $Id: EntityDaoHibernate.java 59 2009-02-09 16:45:24Z tomas.varaneckas $
 */
public abstract class EntityDaoHibernate<T extends PersistentEntity> implements
        EntityDao<T> {

    /** 
     * Logger
     */
    private Log log = LogFactory.getLog(getClass());

    /**
     * {@link Class} that is persisted by this DAO instance 
     */
    final protected Class<T> persistentClass;

    /**
     * JPA Enitity Manager
     */
    @PersistenceContext
    protected EntityManager entityManager;

    /**
     * Constructor that sets the persistent class with help of reflection
     * @see #persistentClass
     */
    @SuppressWarnings("unchecked")
    public EntityDaoHibernate() {
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()[0];
    }

    /**
     * Gets the {@link Class} persisted by this DAO instance
     * @return
     */
    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public boolean deleteById(final Long id) {
        try {
            entityManager.remove(entityManager.find(getPersistentClass(), id));
            return true;
        } catch (final Exception e) {
            log.warn("Failed deleting entity of class: " 
                    + getPersistentClass().getSimpleName()
                    + "; with id: " + id + ": " 
                    + HibernateUtils.getErrorMessage(e));
            log.trace(e);
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        try {
            return entityManager.createQuery("select e from " 
                    + getPersistentClass().getSimpleName() 
                    + " e").getResultList();
        } catch (final Exception e) {
            log.warn("Failed finding all instances of " + getPersistentClass()
                    .getSimpleName() + ": " 
                    + HibernateUtils.getErrorMessage(e));
            log.trace(e);
            return Collections.EMPTY_LIST;
        }
    }

    @SuppressWarnings("unchecked")
    public List<T> findByExample(final T example) {
        final Map<String, Object> bindings = new HashMap<String, Object>();
        final Query q = entityManager.createQuery(HibernateUtils
                .createFindByExampleQuery(example, bindings));
        for (Entry<String, Object> binding : bindings.entrySet()) {
            q.setParameter(binding.getKey(), binding.getValue());
        }
        return q.getResultList();
    }

    public T findById(final Long id) {
        return entityManager.find(getPersistentClass(), id);
    }

    public boolean save(final T entity) {
        try {
            if (entity.getId() == null || entity.getId() == 0L) {
                log.debug("Saving entity:" + entity);
                entityManager.persist(entity);
            } else {
                log.debug("Updating entity: " + entity);
                entityManager.merge(entity);
            }
            return true;
        } catch (final Throwable e) {
            log.warn("Failed saving entity: " + entity + ": " 
                    + HibernateUtils.getErrorMessage(e));
            log.trace(e);
            return false;
        }
    }
}
